#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <time.h>
#include <sys/stat.h>

#define QLEN        10
#define STALE       30  /* client's name can't be older than this (sec) */
#define CLI_PATH    "/var/tmp"  /* +5 for pid = 14 chars */
#define CLI_PERM    0700        /* rwx for the user only */

/*
 * Create a server endpoint of a connection.
 * Returns fd if all ok, <0 on error.
 */
int serv_listen(const char *name)
{
    int fd, size, err, rval;
    struct sockaddr_un un;

    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        return -1;

    unlink(name);   /* om case it already exists */

    /* fill in socket address structure */
    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, name);
    size = offsetof(struct sockaddr_un, sun_path) + strlen(name);

    /* bind the name to the descriptor */
    if (bind(fd, (struct sockaddr *)&un, size) < 0) {
        rval = -2;
        goto errout;
    }

    /* tell kernel we're a server */
    if (listen(fd, QLEN) < 0) {
        rval = -3;
        goto errout;
    }

    return fd;

errout:
    err = errno;
    close(fd);
    errno = err;
    return rval;
}

/*
 *  wait for a client connection to arrive, and accept it.
 *  we also obtain the client's userID from the pathname
 *  that it must be bind before calling us.
 *  Return new fd if all OK, <0 on error.
 */
int serv_accept(int listenfd, uid_t *uidptr)
{
    int                 clifd, len, err, rval;
    time_t              staletime;
    struct sockaddr_un  un;
    struct stat         statbuf;

    len = sizeof(un);
    if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
        return -1;      /* often errno=EINTR, if signal caught */

    /* obtain the client's uid from its calling address */
    len -= offsetof(struct sockaddr_un, sun_path);  /* len of pathname */
    un.sun_path[len] = 0;       /* null terminate */

    if (stat(un.sun_path, &statbuf) < 0) {
        rval = -2;
        goto errout;
    }

#ifdef S_ISSOCK
    if (S_ISSOCK(statbuf.st_mode) == 0) {
        rval = -3;      /* not a socket */
        goto errout;
    }
#endif

    if ((statbuf.st_mode & 0077) ||
        (statbuf.st_mode & 0700) != 0700) {
        rval = -4;      /* is not rwx------ */
        goto errout;
    }

    staletime = time(NULL) - STALE;
    if (statbuf.st_atime < staletime ||
        statbuf.st_mtime < staletime ||
        statbuf.st_ctime < staletime) {
        rval = -5;      /* inode is too old */
        goto errout;
    }

    if (uidptr != NULL)
        *uidptr = statbuf.st_uid;    /* return uid of caller */
    unlink(un.sun_path);
    return (clifd);

errout:
    err = errno;
    close(clifd);
    errno = err;
    return rval;
}

/*
 * Create a client endpoint and connect to a server.
 * Returns fd if all ok, <0 on error.
 */
int cli_conn(const char *name)
{
    int                 fd, len, err, rval;
    struct sockaddr_un  un;

    /* create a unix domain stream socket */
    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        return -1;

    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());
    len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
    
    unlink(un.sun_path);

    if (bind(fd, (struct sockaddr *)&un, len) < 0) {
        rval = -2;
        goto errout;
    }

    if (chmod(un.sun_path, CLI_PERM) < 0) {
        rval = -3;
        goto errout;
    }
    
    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, name);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
    if (connect(fd, (struct sockaddr *)&un, len) < 0) {
        rval = -4;
        goto errout;
    }

    return fd;

errout:
    err = errno;
    close(fd);
    errno = err;
    return rval;
}
